Allow selectively cleaning packages
authorAlex Crichton <alex@alexcrichton.com>
Tue, 23 Sep 2014 16:03:34 +0000 (09:03 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Mon, 29 Sep 2014 23:54:24 +0000 (16:54 -0700)
This adds a new argument to `cargo clean` which will enable selectively cleaning
particular packages. The command only cleans the package specified, no other
(not the dependencies of the package).

cc #537

src/bin/clean.rs
src/cargo/ops/cargo_clean.rs
src/cargo/ops/cargo_rustc/fingerprint.rs
src/cargo/ops/cargo_rustc/layout.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/ops/mod.rs
tests/test_cargo_compile_git_deps.rs

index a671d05084f7f6216cf2ef4bed750f71f7a82f3c..fac0c9c3dafab3d81e78430b961ff115d2db2968 100644 (file)
@@ -10,20 +10,34 @@ docopt!(Options, "
 Remove artifacts that cargo has generated in the past
 
 Usage:
-    cargo clean [options]
+    cargo clean [options] [<spec>]
 
 Options:
     -h, --help              Print this message
     --manifest-path PATH    Path to the manifest to the package to clean
+    --target TRIPLE         Target triple to clean output for (default all)
     -v, --verbose           Use verbose output
-",  flag_manifest_path: Option<String>)
 
-pub fn execute(options: Options, _shell: &mut MultiShell) -> CliResult<Option<()>> {
+If <spec> is provided, then it is interpreted as a package id specification and
+only the output for the package specified will be removed. If <spec> is not
+provided, then all output from cargo will be cleaned out. Note that a lockfile
+must exist for <spec> to be given.
+
+For more information about <spec>, see `cargo help pkgid`.
+",  flag_manifest_path: Option<String>, arg_spec: Option<String>,
+    flag_target: Option<String>)
+
+pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
+    shell.set_verbose(options.flag_verbose);
     debug!("executing; cmd=cargo-clean; args={}", os::args());
 
     let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path));
-
-    ops::clean(&root).map(|_| None).map_err(|err| {
+    let mut opts = ops::CleanOptions {
+        shell: shell,
+        spec: options.arg_spec.as_ref().map(|s| s.as_slice()),
+        target: options.flag_target.as_ref().map(|s| s.as_slice()),
+    };
+    ops::clean(&root, &mut opts).map(|_| None).map_err(|err| {
       CliError::from_boxed(err, 101)
     })
 }
index d8362f23bbcb76fc17c99754a3a258ce0138a83b..cb8786a478f4c93e511a352d509a5f0f098eae97 100644 (file)
@@ -1,30 +1,81 @@
-use std::io::fs::{rmdir_recursive, PathExtensions};
+use std::io::fs::{mod, PathExtensions};
 
-use core::source::Source;
+use core::{MultiShell, PackageSet};
+use core::source::{Source, SourceMap};
 use sources::PathSource;
-use util::{CargoResult, human, ChainError};
+use util::{CargoResult, human, ChainError, Config};
+use ops::{mod, Layout, Context};
 
-/// Cleans the project from build artifacts.
+pub struct CleanOptions<'a> {
+    pub spec: Option<&'a str>,
+    pub target: Option<&'a str>,
+    pub shell: &'a mut MultiShell<'a>
+}
 
-pub fn clean(manifest_path: &Path) -> CargoResult<()> {
+/// Cleans the project from build artifacts.
+pub fn clean(manifest_path: &Path, opts: &mut CleanOptions) -> CargoResult<()> {
     let mut src = try!(PathSource::for_path(&manifest_path.dir_path()));
     try!(src.update());
     let root = try!(src.get_root_package());
     let manifest = root.get_manifest();
 
-    let build_dir = manifest.get_target_dir();
-    if build_dir.exists() {
-        try!(rmdir_recursive(build_dir).chain_error(|| {
-            human("Could not remove build directory")
-        }))
-    }
+    // If we have a spec, then we need to delete some package,s otherwise, just
+    // remove the whole target directory and be done with it!
+    let spec = match opts.spec {
+        Some(spec) => spec,
+        None => return rm_rf(manifest.get_target_dir()),
+    };
+
+    // Load the lockfile (if one's available), and resolve spec to a pkgid
+    let lockfile = root.get_root().join("Cargo.lock");
+    let source_id = root.get_package_id().get_source_id();
+    let resolve = match try!(ops::load_lockfile(&lockfile, source_id)) {
+        Some(resolve) => resolve,
+        None => return Err(human("A Cargo.lock must exist before cleaning"))
+    };
+    let pkgid = try!(resolve.query(spec));
+
+    // Translate the PackageId to a Package
+    let mut cfg = try!(Config::new(opts.shell, None, None));
+    let pkg = {
+        let mut source = pkgid.get_source_id().load(&mut cfg);
+        try!(source.update());
+        (try!(source.get([pkgid.clone()]))).into_iter().next().unwrap()
+    };
 
-    let doc_dir = manifest.get_doc_dir();
-    if doc_dir.exists() {
-        try!(rmdir_recursive(doc_dir).chain_error(|| {
-            human("Could not remove documentation directory")
-        }))
+    // Create a compilation context to have access to information like target
+    // filenames and such
+    let srcs = SourceMap::new();
+    let pkgs = PackageSet::new([]);
+    let cx = try!(Context::new("compile", &resolve, &srcs, &pkgs, &mut cfg,
+                               Layout::at(root.get_absolute_target_dir()),
+                               None));
+
+    // And finally, clean everything out!
+    for target in pkg.get_targets().iter() {
+        let layout = Layout::new(&root, opts.target,
+                                 target.get_profile().get_dest());
+        try!(rm_rf(&layout.native(&pkg)));
+        try!(rm_rf(&layout.fingerprint(&pkg)));
+        for filename in try!(cx.target_filenames(target)).iter() {
+            let filename = filename.as_slice();
+            try!(rm_rf(&layout.dest().join(filename)));
+            try!(rm_rf(&layout.deps().join(filename)));
+        }
     }
 
     Ok(())
 }
+
+fn rm_rf(path: &Path) -> CargoResult<()> {
+    if path.is_dir() {
+        try!(fs::rmdir_recursive(path).chain_error(|| {
+            human("could not remove build directory")
+        }));
+    } else if path.exists() {
+        try!(fs::unlink(path).chain_error(|| {
+            human("failed to remove build artifact")
+        }));
+    }
+    Ok(())
+}
index c0ca783c3e269cab39e3ac6c3b9e6532c476536e..f1840da877dbf52e6d90b417c6876ffa46aa6311 100644 (file)
@@ -5,7 +5,6 @@ use std::io::{fs, File, UserRWX, BufferedReader};
 
 use core::{Package, Target, PathKind};
 use util;
-use util::hex::short_hash;
 use util::{CargoResult, Fresh, Dirty, Freshness, internal, Require, profile};
 
 use super::{Kind, KindTarget};
@@ -194,12 +193,9 @@ fn prepare(is_fresh: bool, loc: Path, fingerprint: String,
 
 /// Return the (old, new) location for fingerprints for a package
 pub fn dirs(cx: &Context, pkg: &Package, kind: Kind) -> (Path, Path) {
-    let dirname = format!("{}-{}", pkg.get_name(),
-                          short_hash(pkg.get_package_id()));
-    let dirname = dirname.as_slice();
     let layout = cx.layout(kind);
     let layout = layout.proxy();
-    (layout.old_fingerprint().join(dirname), layout.fingerprint().join(dirname))
+    (layout.old_fingerprint(pkg), layout.fingerprint(pkg))
 }
 
 /// Returns the (old, new) location for the dep info file of a target.
index 59025ed6659426e00fcee991b8187889cfb9c4ef..82397d3f5286d4ff76763a31a94af48d6f51c788 100644 (file)
@@ -70,7 +70,20 @@ pub struct LayoutProxy<'a> {
 }
 
 impl Layout {
-    pub fn new(root: Path) -> Layout {
+    pub fn new(pkg: &Package, triple: Option<&str>, dest: Option<&str>) -> Layout {
+        let mut path = pkg.get_absolute_target_dir();
+        match triple {
+            Some(s) => path.push(s),
+            None => {}
+        }
+        match dest {
+            Some(s) => path.push(s),
+            None => {}
+        }
+        Layout::at(path)
+    }
+
+    pub fn at(root: Path) -> Layout {
         Layout {
             deps: root.join("deps"),
             native: root.join("native"),
@@ -127,18 +140,22 @@ impl Layout {
     pub fn dest<'a>(&'a self) -> &'a Path { &self.root }
     pub fn deps<'a>(&'a self) -> &'a Path { &self.deps }
     pub fn native(&self, package: &Package) -> Path {
-        self.native.join(self.native_name(package))
+        self.native.join(self.pkg_dir(package))
+    }
+    pub fn fingerprint(&self, package: &Package) -> Path {
+        self.fingerprint.join(self.pkg_dir(package))
     }
-    pub fn fingerprint(&self) -> &Path { &self.fingerprint }
 
     pub fn old_dest<'a>(&'a self) -> &'a Path { &self.old_root }
     pub fn old_deps<'a>(&'a self) -> &'a Path { &self.old_deps }
     pub fn old_native(&self, package: &Package) -> Path {
-        self.old_native.join(self.native_name(package))
+        self.old_native.join(self.pkg_dir(package))
+    }
+    pub fn old_fingerprint(&self, package: &Package) -> Path {
+        self.old_fingerprint.join(self.pkg_dir(package))
     }
-    pub fn old_fingerprint(&self) -> &Path { &self.old_fingerprint }
 
-    fn native_name(&self, pkg: &Package) -> String {
+    fn pkg_dir(&self, pkg: &Package) -> String {
         format!("{}-{}", pkg.get_name(), short_hash(pkg.get_package_id()))
     }
 }
index 5da63efe4221a746556bd40333f97b82ba7fa388..307743177f1df9cee31167abd4eb0fa53b73cf56 100644 (file)
@@ -11,10 +11,12 @@ use util::{Config, internal, ChainError, Fresh, profile};
 use self::job::{Job, Work};
 use self::job_queue::{JobQueue, StageStart, StageCustomBuild, StageLibraries};
 use self::job_queue::{StageBinaries, StageEnd};
-use self::context::{Context, PlatformRequirement, PlatformTarget};
-use self::context::{PlatformPlugin, PlatformPluginAndTarget};
 
 pub use self::compilation::Compilation;
+pub use self::context::Context;
+pub use self::context::{PlatformPlugin, PlatformPluginAndTarget};
+pub use self::context::{PlatformRequirement, PlatformTarget};
+pub use self::layout::{Layout, LayoutProxy};
 
 mod context;
 mod compilation;
@@ -24,7 +26,7 @@ mod job_queue;
 mod layout;
 
 #[deriving(PartialEq, Eq)]
-enum Kind { KindPlugin, KindTarget }
+pub enum Kind { KindPlugin, KindTarget }
 
 // This is a temporary assert that ensures the consistency of the arguments
 // given the current limitations of Cargo. The long term fix is to have each
@@ -57,11 +59,10 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
 
     debug!("compile_targets; targets={}; pkg={}; deps={}", targets, pkg, deps);
 
-    let root = pkg.get_absolute_target_dir();
-    let dest = uniq_target_dest(targets).unwrap_or("");
-    let host_layout = layout::Layout::new(root.join(dest));
+    let dest = uniq_target_dest(targets);
+    let host_layout = Layout::new(pkg, None, dest);
     let target_layout = config.target().map(|target| {
-        layout::Layout::new(root.join(target).join(dest))
+        layout::Layout::new(pkg, Some(target), dest)
     });
 
     let mut cx = try!(Context::new(env, resolve, sources, deps, config,
index c3c584ac318b220a034760302de8efb43659621d..ddf5c2b9f8c1ce54eb886157b221eb5cf26381df 100644 (file)
@@ -1,7 +1,10 @@
-pub use self::cargo_clean::clean;
+pub use self::cargo_clean::{clean, CleanOptions};
 pub use self::cargo_compile::{compile, CompileOptions};
 pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
-pub use self::cargo_rustc::{compile_targets, Compilation};
+pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind};
+pub use self::cargo_rustc::{KindTarget, KindPlugin, Context, LayoutProxy};
+pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget};
+pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget};
 pub use self::cargo_run::run;
 pub use self::cargo_new::{new, NewOptions};
 pub use self::cargo_doc::{doc, DocOptions};
index 283113bacc1cfbfee288d3087c8c672cdadc5e39..8794889000fc56cc5066ec63eaf85b3d49c054b2 100644 (file)
@@ -605,6 +605,13 @@ test!(recompilation {
                                              {} foo v0.5.0 ({})\n",
                                             COMPILING, git_project.url(),
                                             COMPILING, p.url())));
+
+    // Make sure clean only cleans one dep
+    assert_that(p.process(cargo_dir().join("cargo")).arg("clean").arg("foo"),
+                execs().with_stdout(""));
+    assert_that(p.process(cargo_dir().join("cargo")).arg("build"),
+                execs().with_stdout(format!("{} foo v0.5.0 ({})\n",
+                                            COMPILING, p.url())));
 })
 
 test!(update_with_shared_deps {